package vv.lines.store.jaxb;

import vv.lines.store.GameChampion;
import vv.lines.store.IChampionsStore;
import vv.lines.store.jaxb.model.Champion;
import vv.lines.store.jaxb.model.Champions;
import vv.lines.store.jaxb.model.ObjectFactory;
import vv.lines.utils.LinesConstants;

import javax.xml.bind.*;
import java.io.File;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import static javax.xml.bind.Marshaller.JAXB_FORMATTED_OUTPUT;
import static vv.lines.utils.LinesConstants.JAXB_MODEL_PACKAGE;
import static vv.lines.utils.LinesConstants.UNNAMED_CHAMPION;

/**
 * @author vpotapenko
 */
public class JaxbChampionsStore implements IChampionsStore {

    private static final Logger logger = Logger.getLogger(JaxbChampionsStore.class.getName());
    private static final ObjectFactory OBJECT_FACTORY = new ObjectFactory();
    private static final Comparator<GameChampion> SCORE_COMPARATOR = new Comparator<GameChampion>() {
        @Override
        public int compare(GameChampion o1, GameChampion o2) {
            return o2.getScore() - o1.getScore();
        }
    };

    private List<GameChampion> gameChampions = new ArrayList<GameChampion>();
    private final String fileName;
    private final int maxChampions;

    private Unmarshaller unmarshaller;
    private Marshaller marshaller;

    public JaxbChampionsStore(String fileName, int maxChampions) {
        this.fileName = fileName;
        this.maxChampions = maxChampions;
        init();
    }

    private void init() {
        File file = new File(fileName);
        if (file.exists()) loadChampions(file);

        while (gameChampions.size() < maxChampions) {
            gameChampions.add(UNNAMED_CHAMPION);
        }
    }

    private void loadChampions(File file) {
        try {
            Unmarshaller unmarshaller = getUnmarshaller();
            JAXBElement<Champions> jaxbElement =
                    (JAXBElement<Champions>) unmarshaller.unmarshal(file);
            fillLinesChampions(jaxbElement.getValue().getChampion());
        } catch (JAXBException e) {
            logger.log(Level.SEVERE, e.getMessage(), e);
        }
    }

    private void fillLinesChampions(List<Champion> champions) {
        for (Champion champion : champions) {
            gameChampions.add(createGameChampion(champion));
        }
    }

    private GameChampion createGameChampion(Champion champion) {
        final String name = champion.getName();
        final BigInteger score = champion.getScore();
        return (name == null || score == null) ? LinesConstants.UNNAMED_CHAMPION :
                new GameChampion(name, score.intValue());
    }

    private Unmarshaller getUnmarshaller() throws JAXBException {
        if (unmarshaller == null) {
            JAXBContext jaxbContext = getContext();
            unmarshaller = jaxbContext.createUnmarshaller();
        }
        return unmarshaller;
    }

    private JAXBContext getContext() throws JAXBException {
        return JAXBContext.newInstance(JAXB_MODEL_PACKAGE);
    }

    @Override
    public List<GameChampion> getChampions() {
        return gameChampions;
    }

    @Override
    public boolean isNewRecord(int score) {
        for (GameChampion champion : gameChampions) {
            if (champion.getScore() < score)
                return true;
        }
        return false;
    }

    @Override
    public void addChampion(GameChampion champion) {
        gameChampions.add(champion);
        removeExcessChampions();
    }

    private void removeExcessChampions() {
        Collections.sort(gameChampions, SCORE_COMPARATOR);
        gameChampions = gameChampions.subList(0, Math.min(maxChampions, gameChampions.size()));
    }

    @Override
    public void save() {
        try {
            Marshaller marshaller = getMarshaller();
            marshaller.marshal(createChampions(), new File(fileName));
        } catch (JAXBException e) {
            logger.log(Level.SEVERE, e.getMessage(), e);
        }
    }

    private Object createChampions() {
        Champions champions = OBJECT_FACTORY.createChampions();
        for (GameChampion gameChampion : gameChampions) {
            champions.getChampion().add(createJaxbChampion(gameChampion));
        }
        return OBJECT_FACTORY.createChampions(champions);
    }

    private Champion createJaxbChampion(GameChampion gameChampion) {
        Champion champion = OBJECT_FACTORY.createChampion();

        champion.setName(gameChampion.getName());
        champion.setScore(BigInteger.valueOf(gameChampion.getScore()));

        return champion;
    }

    private Marshaller getMarshaller() throws JAXBException {
        if (marshaller == null) {
            JAXBContext jaxbContext = getContext();
            marshaller = jaxbContext.createMarshaller();
            marshaller.setProperty(JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
        }
        return marshaller;
    }
}
